/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.utils.common.security;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
/**
* Provides an abstract base class checking for potential security issues related to deserialization of data received from external sources.
* The abstract base class approach is used to ensure that the actual test runs in the classpath and bundle context of the caller, not the
* one of a common utility class.
*
* @author Robert Mischke
*/
public abstract class AbstractDeserializationClasspathCheck {
private static final String[] KNOWN_COMMONS_COLLECTIONS_ROOT_NAMESPACES = new String[] {
"org.apache.commons.collections.",
"org.apache.commons.collections4.",
"org.apache.commons.collections15."
};
private static final String[] KNOWN_UNSAFE_OR_SUSPICIOUS_COMMONS_COLLECTIONS_CLASSES = new String[] {
"Transformer",
"Factory",
"functors.ConstantTransformer",
"functors.ConstantTransformer",
"functors.ClosureTransformer",
"functors.InvokerTransformer",
"functors.InstantiateFactory",
"functors.InstantiateTransformer",
"functors.PrototypeFactory",
// expand as necessary
};
private static final String[] OTHER_UNSAFE_OR_SUSPICIOUS_CLASSES = new String[] {
"org.codehaus.groovy.runtime.ConvertedClosure",
// TODO find out if this class is really affected
// "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
// expand as necessary
};
private final Log log = LogFactory.getLog(getClass());
/**
* Checks the current classpath for classes known or suspected to be unsafe for deserialization of external data.
*
* @return
*
* @return true if problems were detected; false if all tests passed successfully
*/
public boolean checkForKnownUnsafeClassesInClasspath() {
// as this test checks for the ABSENCE of certain things, make sure the test patterns don't contain unintended characters
final Pattern typoGuard = Pattern.compile("^[a-zA-Z][a-zA-Z0-9\\.]+[a-zA-Z]$");
final Bundle bundle = FrameworkUtil.getBundle(getClass());
log.debug("Running in context of bundle " + bundle + " (may be 'null' when not running in an OSGi context)");
boolean unsafeClassFound = false;
for (String className : assembleListOfSuspiciousOrKnownUnsafeClassesForDeserialization()) {
if (!typoGuard.matcher(className).matches()) {
throw new IllegalArgumentException("The class name seems to be malformed: " + className);
}
try {
Class.forName(className);
log.error("Known unsafe class found in classpath: " + className);
unsafeClassFound = true;
} catch (ClassNotFoundException e) {
try {
Thread.currentThread().getContextClassLoader().loadClass(className);
log.error("Known unsafe class found via context classloader: " + className);
unsafeClassFound = true;
} catch (ClassNotFoundException e2) {
try {
if (bundle != null) {
bundle.loadClass(className);
log.error("Known unsafe class found via bundle classloader: " + className);
unsafeClassFound = true;
}
} catch (ClassNotFoundException e3) {
// ok
log.debug("Not found in classpath (good): " + className);
}
}
}
}
return unsafeClassFound;
}
private List<String> assembleListOfSuspiciousOrKnownUnsafeClassesForDeserialization() {
List<String> list = new ArrayList<>();
for (String prefix : KNOWN_COMMONS_COLLECTIONS_ROOT_NAMESPACES) {
for (String suffix : KNOWN_UNSAFE_OR_SUSPICIOUS_COMMONS_COLLECTIONS_CLASSES) {
list.add(prefix + suffix);
}
}
for (String classname : OTHER_UNSAFE_OR_SUSPICIOUS_CLASSES) {
list.add(classname);
}
return list;
}
}